package gui.batches;

import gui.inventory.ProductContainerData;
import gui.item.ItemData;
import hitExceptions.InvalidDataException;
import hitExceptions.InvalidOperationException;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Timer;

import reports.PDFGenerator;

import com.itextpdf.text.DocumentException;

import coreModel.Barcode;
import coreModel.Item;
import coreModel.Product;
import coreModel.ProductContainer;
import coreModel.StorageUnit;

/**
 * Controller class for the add item batch view.
 */
public class AddItemBatchController extends ItemBatchController implements
IAddItemBatchController {

	/**
	 * Constructor.
	 * 
	 * @param view Reference to the add item batch view.
	 * @param target Reference to the storage unit to which items are being added.
	 */
	public AddItemBatchController(ItemBatchView view, ProductContainerData target) 
	{
		super(view, target);

		//initialize view
		getView().setEntryDate(new Date());
		getView().setCount("1");

		construct();

	}

	/**
	 * Returns a reference to the view for this controller.
	 */
	protected AddItemBatchView getView()
	{	
		return (AddItemBatchView) super.getView();
	}

	/**
	 * Sets the enable/disable state of all components in the add item batch view.
	 * A component should be enabled only if the user is currently
	 * allowed to interact with that component.
	 * 
	 * {@pre None}
	 * 
	 * {@post The enable/disable state of all components in the add item batch view
	 * have been set appropriately.}
	 */
	@Override
	protected void enableComponents() 
	{
		getView().enableItemAction(!useScanner&&enableTest());

		//enable undo/redo if their stacks aren't empty
		getView().enableUndo(!undoStack.isEmpty());
		getView().enableRedo(!redoStack.isEmpty());
	}

	/** Determines if the "Add Item" button may be enabled, according to
	 *  the barcode and count fields.
	 * 
	 * @return true if "Add Item" may be enabled, false otherwise.
	 */
	private boolean enableTest()
	{
		boolean barcodeEmpty = (getView().getBarcode().length() == 0);
		boolean countValid;
		try 
		{ 
			int x=Integer.parseInt(getView().getCount()); 
			//added so count can't be negative or zero
			if(x<=0)
				countValid=false;
			else
				countValid = true;
		}
		catch (NumberFormatException e)
		{
			return false;
		}
		// added a date check to make sure the dates are valid
		Date date= getView().getEntryDate();
		return(!barcodeEmpty && countValid && Product.isValidDate(date));
	}

	/**
	 * This method is called when the "Entry Date" field in the
	 * add item batch view is changed by the user.
	 */
	@Override
	public void entryDateChanged() 
	{
		//added for date check
		enableComponents();
	}

	/**
	 * This method is called when the "Count" field in the
	 * add item batch view is changed by the user.
	 */
	@Override
	public void countChanged() 
	{
		enableComponents();
	}


	/**
	 * This method is called when the "Product Barcode" field in the
	 * add item batch view is changed by the user.
	 */
	@Override
	public void barcodeChanged() 
	{
		//added to automatically getting the barcode
		if(useScanner && notScheduled)
		{
			notScheduled=false;
			BarcodeReaderForAdding br= new BarcodeReaderForAdding();
			try
			{
				timer.schedule(br, BARCODE_TIME);
			}
			catch (IllegalStateException e) //should reach here iff timer was canceled
			{
				timer = new Timer("barCode thread");
				timer.schedule(br, BARCODE_TIME);
			}
		}
		else if(!useScanner)
		{
			enableComponents();
			timer.cancel();

		}
	}

	/**
	 * This method is called when the "Use Barcode Scanner" setting in the
	 * add item batch view is changed by the user.
	 */
	@Override
	public void useScannerChanged() 
	{
		if(getView().getUseScanner()!=useScanner) //added to fix bug
		{
			getView().setBarcode("");
			useScanner=getView().getUseScanner();
			getView().setCount("1");
		}

		enableComponents();
	}

	/**
	 * This method is called when the user clicks the "Add Item" button
	 * in the add item batch view.
	 */
	@Override
	public void addItem() 
	{

		Product addedProduct = null;
		boolean prodAddedToSystem = false;
		
		int count = 0;
		
		try 
		{
			count = Integer.parseInt(getView().getCount());
		}
		catch (NumberFormatException e)
		{
			getView().displayErrorMessage("Invalid Count");
			count = 0;
		}
		
		//added to solve a problem with notify
		ArrayList<Item> itemList = new ArrayList<Item>();
		
		//add #count items of the specified product barcode  
		for (int i = 0; i < count; i++)
		{
			
			//get the barcode from the view
			Barcode pBarcode = new Barcode(getView().getBarcode());

			//find the product for the barcode. If none exists, add one using the 
			// "Add Product" dialog box.
			try 
			{
				Product dummyProd = new Product("...", pBarcode);
				if (!productManager.productExists(dummyProd))
				{
					getView().displayAddProductView();

					//if the product still isn't in the system, the add-product action must 
					// have been cancelled. (Or it failed. We assume not.) Abort function.
					if (!productManager.productExists(dummyProd))
					{
						//if it is canceled the barcode should be cleared and count set to 1
						getView().setBarcode("");
						getView().setCount("1");
						getView().setEntryDate(new Date());
						return; //ABORT FUNCTION!
					}
					
					//set addedProduct
					addedProduct = productManager.getProductByBarcode(pBarcode);
					prodAddedToSystem = true;
				}
			} 
			catch (InvalidDataException e) 
			{
				System.err.println("Error1 in AddItemBatchController"
						+ "::addItem(): \n\t" + e);
				return;
			}
			Product p = productManager.getProductByBarcode(pBarcode);

			//find the Product Container in target that contains the item's product
			ProductContainer parent = productManager.locationOfProductInContainer(p, target);

			//if target doesn't contain the product anywhere, add the product to it
			if (parent == null)
			{
				//add the product to the target
				try 
				{
					productManager.addProductToContainer(p, target);
				}
				catch (InvalidOperationException e)
				{
					System.err.println("Error2a in AddItemBatchController"
							+ "::addItem(): \n\t" + e);
					return;
				}
				//set addedProduct
				addedProduct = p;

				parent = target;
			}
			//create the item object to add
			Item newItem;
			try 
			{
				newItem = new Item(new Barcode(), p, parent);
			} 
			catch (InvalidDataException e) 
			{
				System.err.println("Error3 in AddItemBatchController"
						+ "::addItem(): \n\t" + e);
				return;
			}
			try 
			{
				newItem.setEntryDate(getView().getEntryDate());
			} 
			catch (InvalidDataException e) 
			{
				System.err.println("Error4 in AddItemBatchController"
						+ "::addItem(): \n\t" + e);
				return;
			}

			//add the item
			itemList.add(newItem);
		
		}
		
		redoStack.clear();
		
		Addition items = new Addition(itemList, addedProduct, prodAddedToSystem);
		items.redo();
		
		undoStack.push(items);
		


		//reinitialize view
		getView().setEntryDate(new Date());
		getView().setCount("1");
		getView().giveBarcodeFocus();
		loadValues();
		enableComponents();
			
		
	}

	/**
	 * This method is called when the user clicks the "Redo" button
	 * in the add item batch view.
	 */
	@Override
	public void redo() 
	{
		
		redoStack.peek().redo();
		undoStack.push(redoStack.pop());
		loadValues();
		enableComponents();
			
	}

	/**
	 * This method is called when the user clicks the "Undo" button
	 * in the add item batch view.
	 */
	@Override
	public void undo() 
	{	
		
		undoStack.peek().undo();
		redoStack.push(undoStack.pop());
		loadValues();
		enableComponents();

	}

	/**
	 * This method is called when the user clicks the "Done" button
	 * in the add item batch view.
	 */
	@Override
	public void done() 
	{
		//generate a barcode pdf file
	
		PDFGenerator pdfGen = new PDFGenerator();
		//create a list of all items that have been added
		ArrayList<ItemData> itemsToLoad = new ArrayList<ItemData>();
		for (Command command : undoStack)
		{
											
					for(int i=0;i<command.getItems().size();i++)
					{
						
						ItemData itemData = new ItemData(command.getItems().get(i));
						itemData.setStorageUnit(target.getName());
						itemsToLoad.add(itemData);
									
					}
				
			
			
		}
		if(itemsToLoad.size()==0)
		{
			getView().close();
			return;
		}
		//add each item to the pdfGen
		for (ItemData iData : itemsToLoad)
		{
			Item item = (Item) iData.getTag();

			pdfGen.addItem(item);
		}
		//print the pdf to a file
		try
		{
			pdfGen.printToFile(); 
		}
		catch (IOException e)
		{
			System.err.println("Error0 in AddItemBatchController::done: "
					+ "\n\t" + e);
		}
		catch (DocumentException e)
		{
			System.err.println("Error1 in AddItemBatchController::done: "
					+ "\n\t" + e);
		}

		getView().close();

		//open the PDF barcodes file
		try
		{
			java.awt.Desktop.getDesktop().open(new File(pdfGen.getFileName()));
		}
		catch (IOException e)
		{
			System.err.println("Error2 in AddItemBatchController::done: "
					+ "\n\t" + e);
		}
		
		
	}
	
	
	
	
	/**
	 * Private class for keeping track of additions we might undo/redo 
	 * in the add item batch view.
	 */
	
	
	
	private class Addition extends Command
	{
		protected Date prevProdDate = null; //leave null if product date is not changed
		protected boolean addedProductToSystem;

		/**
		 * Basic constructor.
		 * @param prevProdLoc 
		 * 
		 * @param item The item to be added.
		 */
	
				
		public Addition(ArrayList<Item> i, Product addedProduct, boolean addedProductToSystem)
		{
			items = i;
			this.addedProduct = addedProduct;
			this.addedProductToSystem = addedProductToSystem;
		}


		
		public ArrayList<Item> getItems()
		{
			return items;
		}
		
		
		public void redo()
		{
			try
			{
				Product itemProduct = items.get(0).getProduct();
	
				if (prevProdDate == null) //if this is the first time it will be null
				{
					//change product date if it isn't before the entry date for the item(s)
					Calendar prodCal = GregorianCalendar.getInstance();
					Calendar itemCal = GregorianCalendar.getInstance();
					prodCal.setTime(itemProduct.getCreationDate());
					itemCal.setTime(getView().getEntryDate());
					if (!prodCal.before(itemCal))
					{
						prevProdDate = itemProduct.getCreationDate();
						itemProduct.setCreationDate(getView().getEntryDate());
					}
				}
				else //if this is the second time, and prevProdDate is not null,
				{	 // then it will have toggled to the newProdDate! We'll toggle it back.

					//switch the product's current date with prevProdDate
					Date newProdDate = itemProduct.getCreationDate();
					itemProduct.setCreationDate(prevProdDate);
					prevProdDate = newProdDate;
				}
				
				//add the items
				itemFacade.addItems(items);
			}
			catch(InvalidDataException e)
			{
				System.err.println("Error5a in AddItemBatchController::Addition::redo: "
						+ "\n\t" + e);
			}
		}
		
		
		/**
		 * Undoes (makes undone) this addition, and all of its effects. This includes 
		 * all additions contained within (if this is compound) and also includes 
		 * any product creation.
		 */
		@Override
		public void undo()
		{
			
			try	
			{
				Product itemProduct = items.get(0).getProduct();
				itemFacade.unaddItems(items);
				if (addedProduct != null)
				{
					productFacade.removeProductFromContainer(addedProduct, target);
					if (addedProductToSystem)
						productFacade.removeProductFromSystem(addedProduct);
				}
				else 
				{
					//set the product's date to its old date
					if (prevProdDate != null)
					{
						//switch the product's current date with prevProdDate
						Date newProdDate = itemProduct.getCreationDate();
						itemProduct.setCreationDate(prevProdDate);
						prevProdDate = newProdDate;
					}
				}
			}
			catch(InvalidOperationException e)
			{
				System.err.println("Error4b in AddItemBatchController::Addition::undo: "
						+ "\n\t" + e);
			}
			catch(InvalidDataException e)
			{
				System.err.println("Error5b in AddItemBatchController::Addition::undo: "
						+ "\n\t" + e);
			}
			
		}

	}

	/** Private class for the barcode scanning timer in the add item batch view. 
	 * 
	 */
	private class BarcodeReaderForAdding extends BarcodeReader
	{
		/** 
		 * This will be called when this timer runs out. 
		 */
		@Override
		public void run()
		{
			if(enableTest()) // change to check to enabled support
				addItem();


			notScheduled=true;
			getView().setBarcode("");
			getView().setCount("1");

		}

	}

}

